<?php

/***********************************************************************
* function isDate
*
* boolean isDate(string)
* Summary: checks if a date is formatted correctly: dd/mm/yyyy (european)
* Author: Laurence Veale
* Date: 30/07/2001
***********************************************************************/
function isDate($i_sDate)
{
  $blnValid = true;
   // check the format first (may not be necessary as we use checkdate() below)
   if(!ereg ("^[0-9]{2}/[0-9]{2}/[0-9]{4}$", $i_sDate))
   {
    $blnValid = false;
   }
   else //format is okay, check that days, months, years are okay
   {
      $arrDate = explode("/", $i_sDate); // break up date by slash
      $intDay = $arrDate[0];
      $intMonth = $arrDate[1];
      $intYear = $arrDate[2];

      $intIsDate = checkdate($intMonth, $intDay, $intYear);

     if(!$intIsDate)
     {
        $blnValid = false;
     }

   }//end else

   return ($blnValid);
} //end function isDate



class Formulario {

	/**
		TODO: 
			- incorporar campos como "entero" validado entre unos rangos
			- incorporar campo e-mail
			- incorporar campo url
	*/

	var $elementos = array();
	var $width = 1;
	var $maxInputWidth = 50;
	var $classLabel = "label";
	var $classCampo = "field";

	/** Indica si vamos a utilizar el calendario o no. Esto en realidad lo establecera la extension y no el usuario */
	var $CALENDAR_SUPPORT = true;

	public function CreaInput($key, $label, $required = true, $length = -1, $validate = true) {
                $this->elementos[$key] = (object)array(type => "input", label => $label, length => $length, required => $required, validate => $validate, colspan => 1);
		return true;
	}
	public function CreaDate($key, $label, $required = true, $validate = true) {
		$this->elementos[$key] = (object)array(type => "date", label => $label, length => 10, required => $required, validate => $validate, colspan => 1);
		return true;
	}
	public function CreaDerived($key, $fncName) {
		if (!is_callable($fncName)) return false;
		$this->elementos[$key] = (object)array(type => "derived", fncName => $fncName);
		return true;
	}
	public function CreaText($key, $label, $required = true, $length = -1, $validate = true) {
                $this->elementos[$key] = (object)array(type => "textarea", label => $label, length => $length, required => $required, validate => $validate, colspan => 1);
		return true;
	}
	public function CreaOption($key, $label, $values = "") {
		$valores = explode(",", $values);
		for ($i = 0; $i < count($valores); $i++)
			$valores[$i]=trim($valores[$i]);
		$this->elementos[$key] = (object)array(type => "combo", label => $label, values => $valores, colspan => 1);
		return true;
	}
	public function SetColspan($key, $colspan) {
		if (!array_key_exists($key, $this->elementos)) {
			trigger_error(wfMsg('setcolspan',$key), E_USER_WARNING);
			return false;
		}
		$this->elementos[$key]->colspan = $colspan;
		return true;
	}
	/** El constructor recibe como parametro el tamaño de la fila (la cantidad se refiere a multiplos de LABEL+CAMPO:) */
	public function __construct($width = 1) {
		$this->width = $width;
		global $wgMessageCache;
		$wgMessageCache->loadMessagesFile(dirname(__file__)."/Formulario.i18n.php");
	}

	/** Determina si un campo debe aparecer en un formulario o no (por el tipo) */
	private function ShouldAppearInForm(&$elemento) {
		switch ($elemento->type) {
			case 'input':
			case 'date':
			case 'combo':
			case 'textarea': return true; break;
		}
		return false;
	}
	/** Determina si el formulario es valido o no
		ATENCION: dos sentidos de validez: campos requeridos y campos que no cumplen el "formato"
	*/
	public function validaFormulario() {
		$invalid = array();
		$vacio = array();
		foreach($this->elementos as $id => $elemento) {
			// Solo validamos aquellos "interactivos"
			if ($this->ShouldAppearInForm($elemento)) {
				if ($elemento->validate) {
					$currentValid = true;
					if ($elemento->required && !isset($elemento->value)) {
						array_push($vacio, $id);
						$currentValid = false;
					} else
					if ($elemento->required && isset($elemento->value) && ($elemento->value == '')) {
						array_push($vacio, $id);
						$currentValid = false;
					}
					if ($currentValid) {
						switch ($elemento->type) {
							case 'textarea':
							case 'input':
								// La validacion es que si hay longitud, que no se pase
								if ($elemento->length > 0) {
									if (isset($elemento->value) && (strlen($elemento->value) > $elemento->length))
										$currentValid = false;
								}
								break;
							case 'date':
								// Si no tiene formato de fecha, falla
								if (($elemento->value == '') && (!$elemento->required)) {
								} else
								if (!isDate($elemento->value))
									$currentValid = false;
								break;
							case 'combo':
								// El COMBO fallaria si hubieran hecho "tampering" y el valor no estuviera previsto
								if (!array_key_exists($elemento->value, $elemento->values))
									$currentValid = false;
								break;
						}
						if (!$currentValid) {
							array_push($invalid, $id);
						}
					}
				}
			}
		}
		return (object)array(vacios =>$vacio, invalidos => $invalid);
	}

	/** Esta funcion asigna los valores del array pasado al formulario.
		USOS: hay dos usos: copiar formularios (mediante el objeto generado) y coger de _POST o _GET
		ATENCION: solo coge valores para los datos que son "rellenables"; los derivados no se pueden asignar.
	*/
	function recuperaValores(&$valores) {
		if (!$valores) return 0;
		if (!is_array($valores)) return 0;
		$numeroIntentados = 0;

		foreach($this->elementos as $id => $elemento) {
			if (array_key_exists($id,$valores)) {
				// Asi evitamos que nos "cuelen" valores
				if ($this->ShouldAppearInForm($elemento)) {
					// Los blancos no nos valen
					if (trim($valores[$id])!='') {
						$numeroIntentados++;
						$this->elementos[$id]->value = $valores[$id];
					}
				}
			}
		}
		return $numeroIntentados;
	}

	/** Genera el codigo HTML correspondiente a cada uno de los elementos. Si tiene valor, se lo pone */
	private function generaHTMLElemento($key, &$elemento) {
		$cadena = "<td ";
		$spanOffset = 0;
		if ($elemento->label != null) {
			if ($this->classLabel)
				$cadena = "{$cadena} class=\"{$this->classLabel}\"";
			$cadena = "{$cadena}>{$elemento->label}</td><td ";
			$spanOffset = -1;
		}
		if ($elemento->colspan > 1) {
			$effectiveColspan = ($elemento->colspan * 2) + $spanOffset;
			$cadena = "{$cadena} colspan=\"{$effectiveColspan}\"";
		}
		if ($this->classCampo)
                        $cadena = "{$cadena} class=\"{$this->classCampo}\"";
		$cadena = "{$cadena}>";
		$value = "";

		switch($elemento->type) {
			case 'input':
				$cadena = $cadena . Xml::input($key, ($elemento->length > 0)?min($elemento->length,$this->maxInputWidth):$this->maxInputWidth, (isset($elemento->value))?$elemento->value:false);
				break;
			case 'textarea':
				$cadena = $cadena . Xml::textarea($key, (isset($elemento->value))?$elemento->value:false);
				break;
			case 'combo':
				$cadena = $cadena . "<select id=\"{$key}\" name=\"{$key}\">";
				if (is_array($elemento->values))
					foreach($elemento->values as $valor) {
						$seleccionado = false;
						if (isset($elemento->value)) {
							$seleccionado = ($elemento->value == $valor);
						}
						$cadena = $cadena . Xml::option($valor, $valor, $seleccionado);
					}
				$cadena = $cadena . "</select>";
				break;
			case 'date':
				$cadena = $cadena . Xml::input($key, 10, (isset($elemento->value))?$elemento->value:false,array('id'=>"{$key}"));
				if ($this->CALENDAR_SUPPORT) {
					$cadena = $cadena . "<input type=\"button\" id=\"btn{$key}\" name=\"btn{$key}\">";
					$cadena = $cadena . "<script type=\"text/javascript\">Calendar.setup({inputField:\"{$key}\",ifFormat:\"%d/%m/%Y\",showsTime:false,button:\"btn{$key}\",singleClick:true,step:1});</script>";
				}
				break;
		}	

		$cadena = "{$cadena}</td>";
		return $cadena;
	}

	/** Esta funcion simplemente genera el formulario HTML */
	public function generaCamposHTML() {
		$cadena = Xml::openElement( 'table', array( 'cellpadding' => '0', cellspacing => '0'));

		$cadena = $cadena . "<tr>";

		$leftSpaceInRow = $this->width;
		foreach($this->elementos as $key => $elemento) {
			if ($this->ShouldAppearInForm($elemento)) {
				if ($elemento->colspan > $this->width) {
					trigger_error(wfMsg('formulario-generacampos',$elemento->key),E_USER_WARNING);
					continue;
				}
				if ($leftSpaceInRow <= 0) {
					$cadena = $cadena . "</tr><tr>";
					$leftSpaceInRow = $this->width;
				}
				if ($elemento->colspan > $leftSpaceInRow) {
					// Empezamos una nueva fila
					$cadena = $cadena . "<td colspan=\"".($leftSpaceInRow*2)."\"></td></tr><tr>";
					$leftSpaceInRow = $this->width;
				}
				$cadena = $cadena . $this->generaHTMLElemento($key, $elemento);
				$leftSpaceInRow = $leftSpaceInRow - $elemento->colspan;	
			}
		}
		if ($leftSpaceInRow > 0)
			$cadena = $cadena . "<td colspan=\"".($leftSpaceInRow*2)."\"></td>";

		$cadena = $cadena . "</tr>";
		$cadena = $cadena . Xml::closeElement( 'table');
	
		return $cadena;
	}	

	/** Devuelve el objeto que representa los valores rellenados.
		ATENCION: esto NO valida; simplemente devuelve los valores. Para ver si es valido hay que utilizar la funcion correspondiente
	 */
	public function construyeObjeto() {
		// Construimos un objeto parcial, que utilizaremos temporalmente para llamar a las funciones de derivacion de valores
		$objeto = array();
		foreach ($this->elementos as $key => &$elemento) {
			if (isset($elemento->value)) {
				switch ($elemento->type) {
					// Este es un caso especial para que lo tome bien la plantilla
					case 'textarea': 	$objeto[$key] = "\n".$elemento->value;
							 	break;
					default:
		 						$objeto[$key] = $elemento->value;
								break;
				}
			}
		}
		// Si se produce un error de derivacion, fallamos (seria si hay una funcion no invocable)
		if (!$this->derivaValores((object)$objeto)) 
			return null;

		// Ahora que ya tenemos los valores derivados, construimos el objeto y lo devolvemos
		$objeto = array();
		foreach ($this->elementos as $key => &$elemento) {
			if (isset($elemento->value)) {
				switch ($elemento->type) {
					// Este es un caso especial para que lo tome bien la plantilla
					case 'textarea': 	$objeto[$key] = "\n".$elemento->value;
							 	break;
					default:
		 						$objeto[$key] = $elemento->value;
								break;
				}
			}
		}

		// Devolvemos el objeto
		return (object)$objeto;
	}

	/** Esta funcion resuelve los valores derivados */
	private function derivaValores($objeto) {
		$retorno = true;

		foreach ($this->elementos as $key => &$elemento) {
			if ($elemento->type == "derived") {
				if (is_callable($elemento->fncName)) {
					$elemento->value = call_user_func($elemento->fncName,$objeto);
				}
				else {
					trigger_error(wfMsg('no-callable-func',$elemento->fncName),E_USER_WARNING);
					$retorno = false;
				}
			}
		}
		return $retorno;
	}
}
?>
